﻿using System;
using System.Collections.Generic;
using Jayden.Dll.Sc2.Descriptions;

namespace Jayden.Dll.Sc2
{
	public class GameState
	{
		public class EntityState
		{
			public int Building;
			public int Idle;
			public int Total;

			public int Built { get { return Total - Building; } }

			public List<double> Energy = null;

			public override string ToString()
			{
				return string.Format("{0} + {1} + {2} = {3}", Building, Idle, Total - (Building + Idle), Total);
			}
		}

		private RaceDescription m_Race;
		private long m_Time;
		private double m_Mineral = 50;
		private double m_Vespene;

		private int m_Bases = 0;
		private int m_BasesBuilding = 0;
		private int m_Extractors = 0;

		private int m_WorkerOnMineral = 0;
		private int m_WorkerOnGas = 0;
		private Dictionary<string, object> m_Datas = new Dictionary<string, object>();

		private int m_MineralIncome = 0;
		private int m_GasIncome = 0;

		private int m_FoodUsed;
		private int m_Food;
		private int m_FoodBuilding;

		private List<KeyValuePair<long, ActionDescription>> m_ExecutedActions = new List<KeyValuePair<long, ActionDescription>>();
		private ActionList m_ActionList = new ActionList();

		private List<DelayedActionDescription> m_DelayedActions = new List<DelayedActionDescription>();

		private Dictionary<EntityDescription, EntityState> m_Entities = new Dictionary<EntityDescription,EntityState>();

		public GameState(RaceDescription race)
		{
			m_Race = race;
			Reset();
		}

		public void Add(DelayedActionDescription action) { m_DelayedActions.Add(action); }

		private void AddInternal(EntityDescription entity, int building, int idle, int total, bool inherited)
		{
			if (entity.Equals(Race.Worker))
				WorkerOnMineral += (total - building);
			if (entity.Equals(Race.Base) && !inherited)
			{
				BasesBuilding += building;
				Bases += (total - building);
			}
			if (entity.Equals(Race.Vespene))
				Extractors += (total - building);
			if (entity.Supply > 0)
			{
				if (!inherited)
				{
					Food += entity.Supply * (total - building);
					FoodBuilding += entity.Supply * building;
				}
				else
					Food += entity.Supply * total;
			}
			else if (entity.Supply < 0)
			{
				FoodUsed -= entity.Supply * total;
				Food += entity.Supply * total;
			}
			if (entity.Inherits != null)
				AddInternal(entity.Inherits, building, idle, total, true);
		}

		public void Add(EntityDescription entity, int building, int idle, int total)
		{
			EntityState state;
			if (!m_Entities.TryGetValue(entity, out state))
			{
				state = new EntityState();
				m_Entities.Add(entity, state);
			}
			state.Building += building;
			state.Idle += idle;
			state.Total += total;
			AddInternal(entity, building, idle, total, false);
			if (total != 0)
			{
				entity.OnTotalChanged(this, total);
				if (Race.Id == "Terran" && entity.Id == "MULE")
					UpdateMineralIncome();
			}
		}

		public EntityState this[EntityDescription entity]
		{
			get
			{
				EntityState state;
				if(m_Entities.TryGetValue(entity, out state))
					return state;
				return null;
			}
		}

		public RaceDescription Race { get { return m_Race; } }
		public long Time { get { return m_Time; } set { m_Time = value; } }
		public string TimeString
		{
			get
			{
				long seconds = Time % 60;
				long minutes = Time / 60;
				return string.Format("{0}:{1}", minutes.ToString().PadLeft(2, '0'), seconds.ToString().PadLeft(2, '0'));
			}
		}
		public double Mineral { get { return m_Mineral; } set { m_Mineral = value; } }
		public double Vespene { get { return m_Vespene; } set { m_Vespene = value; } }
		public int FoodUsed { get { return m_FoodUsed; } set { m_FoodUsed = value; } }
		public int Food { get { return m_Food; } set { m_Food = value; } }
		public int FoodBuilding { get { return m_FoodBuilding; } set { m_FoodBuilding = value; } }

		private void UpdateMineralIncome()
		{
			int mules = 0;
			if (m_Race.Id == "Terran")
			{
				EntityState muleState = this[m_Race["MULE"]];
				if (muleState != null)
					mules = muleState.Total;
			}
			m_MineralIncome = Rules.GetMineralsIncome(m_WorkerOnMineral, mules, m_Bases * Rules.MineralPatchPerBase);
		}
		private void UpdateGasIncome()
		{
			m_GasIncome = Rules.GetGasIncome(m_WorkerOnGas, m_Extractors);
		}

		public int Extractors
		{
			get { return m_Extractors; }
			set
			{
				m_Extractors = value;
				UpdateGasIncome();
			}
		}
		public int Bases
		{
			get { return m_Bases; }
			set
			{
				m_Bases = value;
				UpdateMineralIncome();
			}
		}
		public int BasesBuilding { get { return m_BasesBuilding; } set { m_BasesBuilding = value; } }

		public int WorkerOnMineral
		{
			get { return m_WorkerOnMineral; }
			set
			{
				m_WorkerOnMineral = value;
				UpdateMineralIncome();
			}
		}
		public int WorkerOnGas
		{
			get { return m_WorkerOnGas; }
			set
			{
				m_WorkerOnGas = value;
				UpdateGasIncome();
			}
		}

		public int MineralIncome { get { return m_MineralIncome; } }
		public int GasIncome { get { return m_GasIncome; } }

		public IEnumerable<ActionDescription> Actions
		{
			get
			{
				HashSet<string> usedIds = new HashSet<string>();
				foreach (KeyValuePair<EntityDescription, EntityState> pair in m_Entities)
				{
					if (pair.Value != null && pair.Value.Total > 0)
					{
						foreach (ActionDescription action in pair.Key.Actions)
						{
							if (action.IsAccessible(this))
							{
								if (!usedIds.Contains(action.Id))
								{
									usedIds.Add(action.Id);
									yield return action;
								}
							}
						}
					}
				}
			}
		}

		public ActionDescription GetAction(string actionId)
		{
			foreach (KeyValuePair<EntityDescription, EntityState> pair in m_Entities)
			{
				foreach (ActionDescription action in pair.Key.Actions)
				{
					if (action.Id == actionId && action.IsAccessible(this))
						return action;
				}
			}
			return null;
		}

		public bool Execute(ActionDescription action, int timeOut = 300)
		{
			int loop = 0;
			do
			{
				if (action.IsExecutable(this))
				{
					action.Execute(this);
					m_ActionList.Add(action);
					m_ExecutedActions.Add(new KeyValuePair<long, ActionDescription>(m_Time, action));
					return true;
				}

				m_Time += 1;
				foreach (KeyValuePair<EntityDescription, EntityState> pair in m_Entities)
				{
					int count = (pair.Value.Total - pair.Value.Building);
					if (pair.Key.HaveEnergy && count > 0)
					{
						if(pair.Value.Energy == null)
							pair.Value.Energy = new List<double>();
						while(pair.Value.Energy.Count < count)
							pair.Value.Energy.Add(50);
						for (int c = 0; c < count; c++)
						{
							double current = pair.Value.Energy[c];
							pair.Value.Energy[c] = Math.Min(200, current + 0.5625);
						}
					}
				}
				m_Mineral += m_MineralIncome / 60.0;
				m_Vespene += m_GasIncome / 60.0;
				for (int c = 0; c < m_DelayedActions.Count; c++)
				{
					DelayedActionDescription autoAction = m_DelayedActions[c];
					if (autoAction.IsExecutable(this))
					{
						autoAction.Execute(this);
						m_ExecutedActions.Add(new KeyValuePair<long, ActionDescription>(m_Time, autoAction));
						m_DelayedActions.RemoveAt(c);
						c--;
					}
				}
				loop++;
			}
			while (loop < timeOut);

			if (timeOut == 0)
				return false;

			if (action.IsExecutable(this))
			{
				action.Execute(this);
				m_ExecutedActions.Add(new KeyValuePair<long, ActionDescription>(m_Time, action));
				return true;
			}
			return false;
		}

		public object GetData(string key, object defaultValue = null)
		{
			object value;
			if (m_Datas.TryGetValue(key, out value))
				return value;
			return defaultValue;
		}
		public void SetData(string key, object value)
		{
			m_Datas[key] = value;
		}

		public void Reset()
		{
			m_Datas = new Dictionary<string, object>();
			m_ActionList = new ActionList();
			m_Bases = 0;
			m_BasesBuilding = 0;
			m_DelayedActions = new List<DelayedActionDescription>();
			m_Entities = new Dictionary<EntityDescription, EntityState>();
			m_ExecutedActions = new List<KeyValuePair<long, ActionDescription>>();
			m_Extractors = 0;
			m_Food = 0;
			m_FoodBuilding = 0;
			m_FoodUsed = 0;
			m_GasIncome = 0;
			m_Mineral = 50;
			m_MineralIncome = 0;
			m_Time = 0;
			m_Vespene = 0;
			m_WorkerOnGas = 0;
			m_WorkerOnMineral = 0;

			List<EntityDescription> start = new List<EntityDescription>(m_Race.StartEntities);
			foreach (EntityDescription description in start)
				Add(description, 0, 1, 1);
		}

		public void Execute(ActionList actionList)
		{
			Reset();
			foreach (ActionDescription action in actionList)
				Execute(action);
		}

		public void Replay()
		{
			ActionList actionList = new ActionList(ActionList);
			Reset();
			Execute(actionList);
		}

		public ActionList ActionList { get { return m_ActionList; } }
		public ActionDescription LastAction { get { return m_ActionList == null || m_ActionList.Count == 0 ? null : m_ActionList[m_ActionList.Count - 1]; } }

		public bool HaveRequirement(EntityDescription entity)
		{
			EntityState state = this[entity];
			if (state == null || state.Built == 0)
			{
				foreach (EntityDescription inheriting in entity.Inheritings)
				{
					state = this[inheriting];
					if (state != null && state.Built > 0)
						return true;
				}
				return false;
			}
			return true;
		}
	}

	public class SimpleGameState : Dictionary<EntityDescription, int>
	{
		private RaceDescription m_Race;

		public SimpleGameState(RaceDescription race)
		{
			m_Race = race;
			Reset();
		}

		public int Get(EntityDescription e)
		{
			int current = 0;
			TryGetValue(e, out current);
			return current;
		}

		public int Increment(EntityDescription e)
		{
			int current = 0;
			TryGetValue(e, out current);
			// use the count to increment
			current += e.Count;
			this[e] = current;
			return current;
		}

		public int Decrement(EntityDescription e)
		{
			int current = 0;
			TryGetValue(e, out current);
			// but only decrement by one
			current--;
			this[e] = current;
			return current;
		}

		public void Reset()
		{
			List<EntityDescription> start = new List<EntityDescription>(m_Race.StartEntities);
			foreach (EntityDescription description in start)
				Increment(description);
		}
	}
}
